import path from 'path'
import fsPromises from 'fs/promises'
import { exec } from 'child_process'
import dayjs from 'dayjs'
import log from 'electron-log'
import { app, BrowserWindow, clipboard, dialog, ipcMain, nativeTheme, shell } from 'electron'
import { isChildOfDirectory } from 'common/filesystem/paths'
import { isLinux, isOsx, isWindows } from '../config'
import parseArgs from '../cli/parser'
import { normalizeAndResolvePath } from '../filesystem'
import { normalizeMarkdownPath } from '../filesystem/markdown'
import { registerKeyboardListeners } from '../keyboard'
import { selectTheme } from '../menu/actions/theme'
import { dockMenu } from '../menu/templates'
import registerSpellcheckerListeners from '../spellchecker'
import { watchers } from '../utils/imagePathAutoComplement'
import { WindowType } from '../windows/base'
import EditorWindow from '../windows/editor'
import SettingWindow from '../windows/setting'
import {isDirectory2} from "common/filesystem";

class App {
  /**
   * @param {Accessor} accessor The application accessor for application instances.
   * @param {arg.Result} args Parsed application arguments.
   */
  constructor (accessor, args) {
    this._accessor = accessor
    this._args = args || { _: [] }
    this._openFilesCache = []
    this._openFilesTimer = null
    this._windowManager = this._accessor.windowManager
    // this.launchScreenshotWin = null // The window which call the screenshot.
    // this.shortcutCapture = null
    // 确保 language 对象已正确初始化
    if (accessor.language) {
      console.log('Language object is initialized:', accessor.language);
    } else {
      console.error('Language object is undefined');
    }
    this._listenForIpcMain()
  }

  /**
   * The entry point into the application.
   */
  init () {
    // Enable these features to use `backdrop-filter` css rules!
    if (isOsx) {
      app.commandLine.appendSwitch('enable-experimental-web-platform-features', 'true')
    }

    app.on('second-instance', (event, argv, workingDirectory) => {
      const { _openFilesCache, _windowManager } = this
      const args = parseArgs(argv.slice(1))

      const buf = []
      for (const pathname of args._) {
        // Ignore all unknown flags
        if (pathname.startsWith('--')) {
          continue
        }

        const info = normalizeMarkdownPath(path.resolve(workingDirectory, pathname))
        if (info) {
          buf.push(info)
        }
      }

      if (args['--new-window']) {
        this._openPathList(buf, true)
        return
      }

      _openFilesCache.push(...buf)
      if (_openFilesCache.length) {
        this._openFilesToOpen()
      } else {
        const activeWindow = _windowManager.getActiveWindow()
        if (activeWindow) {
          activeWindow.bringToFront()
        }
      }
    })

    app.on('open-file', this.openFile) // macOS only

    app.on('ready', this.ready)

    app.on('window-all-closed', () => {
      // Close all the image path watcher
      console.log('Closing all the image path watcher')
      for (const watcher of watchers.values()) {
        watcher.close()
      }
      this._windowManager.closeWatcher()
      if (!isOsx) {
        app.quit()
      }
    })

    app.on('activate', () => { // macOS only
      // On OS X it's common to re-create a window in the app when the
      // dock icon is clicked and there are no other windows open.
      if (this._windowManager.windowCount === 0) {
        this.ready()
      }
    })

    // Prevent to load webview and opening links or new windows via HTML/JS.
    app.on('web-contents-created', (event, contents) => {
      contents.on('will-attach-webview', event => {
        event.preventDefault()
      })
      contents.on('will-navigate', event => {
        event.preventDefault()
      })
      contents.setWindowOpenHandler(details => {
        return { action: 'deny' }
      })
    })
  }

  async getScreenshotFileName () {
    const screenshotFolderPath = await this._accessor.dataCenter.getItem('screenshotFolderPath')
    const fileName = `${dayjs().format('YYYY-MM-DD-HH-mm-ss')}-screenshot.png`
    return path.join(screenshotFolderPath, fileName)
  }

  ready = () => {
    const { _args: args, _openFilesCache } = this
    const { preferences } = this._accessor

    if (args._.length) {
      for (const pathname of args._) {
        // Ignore all unknown flags
        if (pathname.startsWith('--')) {
          continue
        }

        const info = normalizeMarkdownPath(pathname)
        if (info) {
          _openFilesCache.push(info)
        }
      }
    }

    const {
      startUpAction,
      defaultDirectoryToOpen,
      autoSwitchTheme,
      theme
    } = preferences.getAll()

    if (startUpAction === 'lastState') {
      let directoryToOpen = defaultDirectoryToOpen;
      let filesToOpen = [];

      try {
        // 从 menu 中获取最近使用的文件记录
        const recentDocuments = this._accessor.menu.getRecentlyUsedDocuments();
        console.log('***startUpAction:lastState:recentDocuments:', recentDocuments.length);

        if (recentDocuments.length > 0) {
          const firstPath = recentDocuments[0];
          // 使用 _processFileAndRecentFiles 处理第一个路径及相关文件
          const { rootDirectory, filesToOpen: processedFiles } = this._processFileAndRecentFiles(firstPath);
          directoryToOpen = rootDirectory;
          filesToOpen = processedFiles;
        }
      } catch (error) {
        console.error('获取最近使用的文件记录时出错了:', error);
      }

      console.log('filesToOpen length:', filesToOpen.length)
      // 后续逻辑：打开文件夹和文件
      if (directoryToOpen) {
        const info = normalizeMarkdownPath(directoryToOpen);
        if (info) {
          this._openFilesCache.unshift(info);
          if (filesToOpen.length > 0) {
            console.log('**Opening files:', filesToOpen);
            this._createEditorWindow(directoryToOpen, filesToOpen);
            return; // 直接返回，避免后续重复创建窗口
          }
        }
      }
    }

    // Set initial native theme for theme in preferences.
    const isDarkTheme = /dark/i.test(theme)
    if (autoSwitchTheme === 0 && isDarkTheme !== nativeTheme.shouldUseDarkColors) {
      selectTheme(nativeTheme.shouldUseDarkColors ? 'dark' : 'light')
      nativeTheme.themeSource = nativeTheme.shouldUseDarkColors ? 'dark' : 'light'
    } else {
      nativeTheme.themeSource = isDarkTheme ? 'dark' : 'light'
    }

    let isDarkMode = nativeTheme.shouldUseDarkColors
    ipcMain.on('broadcast-preferences-changed', change => {
      // Set Chromium's color for native elements after theme change.
      if (change.theme) {
        const isDarkTheme = /dark/i.test(change.theme)
        if (isDarkMode !== isDarkTheme) {
          isDarkMode = isDarkTheme
          nativeTheme.themeSource = isDarkTheme ? 'dark' : 'light'
        } else if (nativeTheme.themeSource === 'system') {
          // Need to set dark or light theme because we set `system` to get the current system theme.
          nativeTheme.themeSource = isDarkMode ? 'dark' : 'light'
        }
      }
    })

    if (isOsx) {
      app.dock.setMenu(dockMenu)
    } else if (isWindows) {
      app.setJumpList([{
        type: 'recent'
      }, {
        type: 'tasks',
        items: [{
          type: 'task',
          title: 'New Window',
          description: 'Opens a new window',
          program: process.execPath,
          args: '--new-window',
          iconPath: process.execPath,
          iconIndex: 0
        }]
      }])
    }

    if (_openFilesCache.length) {
      console.log('***_openFilesCache:', _openFilesCache.length)
      this._openFilesToOpen()
    } else {
      this._createEditorWindow()
    }

    // this.shortcutCapture = new ShortcutCapture()
    // if (process.env.NODE_ENV === 'development') {
    //   this.shortcutCapture.dirname = path.resolve(path.join(__dirname, '../../../node_modules/shortcut-capture'))
    // }
    // this.shortcutCapture.on('capture', async ({ dataURL }) => {
    //   const { screenshotFileName } = this
    //   const image = nativeImage.createFromDataURL(dataURL)
    //   const bufferImage = image.toPNG()

    //   if (this.launchScreenshotWin) {
    //     this.launchScreenshotWin.webContents.send('mt::screenshot-captured')
    //     this.launchScreenshotWin = null
    //   }

    //   try {
    //     // write screenshot image into screenshot folder.
    //     await fse.writeFile(screenshotFileName, bufferImage)
    //   } catch (err) {
    //     log.error(err)
    //   }
    // })
  }

  openFile = (event, pathname) => {
    event.preventDefault()
    const info = normalizeMarkdownPath(pathname)
    if (info) {
      this._openFilesCache.push(info)

      if (app.isReady()) {
        // It might come more files
        if (this._openFilesTimer) {
          clearTimeout(this._openFilesTimer)
        }
        this._openFilesTimer = setTimeout(() => {
          this._openFilesTimer = null
          this._openFilesToOpen()
        }, 100)
      }
    }
  }

  // --- private --------------------------------

  /**
   * Creates a new editor window.
   *
   * @param {string} [rootDirectory] The root directory to open.
   * @param {string[]} [fileList] A list of markdown files to open.
   * @param {string[]} [markdownList] Array of markdown data to open.
   * @param {*} [options] The BrowserWindow options.
   * @returns {EditorWindow} The created editor window.
   */
  _createEditorWindow (rootDirectory = null, fileList = [], markdownList = [], options = {}) {
    const editor = new EditorWindow(this._accessor)
    editor.createWindow(rootDirectory, fileList, markdownList, options)
    this._windowManager.add(editor)
    if (this._windowManager.windowCount === 1) {
      this._accessor.menu.setActiveWindow(editor.id)
    }
    return editor
  }

  /**
   * Create a new setting window.
   */
  _createSettingWindow (category) {
    const setting = new SettingWindow(this._accessor)
    setting.createWindow(category)
    this._windowManager.add(setting)
    if (this._windowManager.windowCount === 1) {
      this._accessor.menu.setActiveWindow(setting.id)
    }
  }

  _openFilesToOpen () {
    this._openPathList(this._openFilesCache, false)
  }

  /**
   * Open the path list in the best window(s).
   *
   * @param {string[]} pathsToOpen The path list to open.
   * @param {boolean} openFilesInSameWindow Open all files in the same window with
   * the first directory and discard other directories.
   */
  _openPathList (pathsToOpen, openFilesInSameWindow = false) {
    console.log('------***_openPathList:pathsToOpen:', pathsToOpen.path);
    const { _windowManager } = this
    const openFilesInNewWindow = this._accessor.preferences.getItem('openFilesInNewWindow')

    // 获取最近使用的文件记录
    // Get recently used documents
    const recentDocuments = this._accessor.menu.getRecentlyUsedDocuments();

    for (const { isDir, path } of pathsToOpen) {
      let rootDirectory = null;
      let filesToOpen = [];

      if (isDir) {
        // 打开的是文件夹
        // Opening a directory
        rootDirectory = path;
        console.log('**Opening folder:', rootDirectory);

        // 查找最近使用的文件中属于该文件夹或其子文件的文件，最多打开 5 个
        // Find up to 5 files from recent documents that belong to this directory or its subdirectories
        let count = 0;
        for (let i = 0; i < recentDocuments.length && count < 5; i++) {
          const pathToCheck = recentDocuments[i];
          if (isChildOfDirectory(rootDirectory, pathToCheck)) {
            filesToOpen.push(pathToCheck);
            count++;
          }
        }
      } else {
        // 打开的是文件
        // Opening a file
        const filePath = path;
        rootDirectory = path.dirname(filePath);
        console.log('**Opening file:', filePath);
        filesToOpen.push(filePath);

        // 查找最近使用的文件中属于该文件所在文件夹或其子文件的文件，最多打开 5 个
        // Find up to 5 files from recent documents that belong to this file's directory or its subdirectories
        let count = 1;
        for (let i = 0; i < recentDocuments.length && count < 5; i++) {
          const pathToCheck = recentDocuments[i];
          if (isChildOfDirectory(rootDirectory, pathToCheck)) {
            filesToOpen.push(pathToCheck);
            count++;
          }
        }
      }

      // 后续逻辑：打开文件夹和文件
      // Next step: open directory and files
      if (rootDirectory) {
        const info = normalizeMarkdownPath(rootDirectory);
        if (info) {
          if (openFilesInNewWindow) {
            this._createEditorWindow(rootDirectory, filesToOpen);
          } else {
            const editor = _windowManager.getActiveWindow();
            if (editor) {
              editor.openTabsFromPaths(
                filesToOpen.map(p => normalizeMarkdownPath(p))
                  .filter(i => i && !i.isDir)
                  .map(i => i.path)
              );
            } else {
              this._createEditorWindow(rootDirectory, filesToOpen);
            }
          }
        }
      }
    }

    // Empty the file list
    pathsToOpen.length = 0;
  }

  _openSettingsWindow (category) {
    const settingWins = this._windowManager.getWindowsByType(WindowType.SETTINGS)
    if (settingWins.length >= 1) {
      // A setting window is already created
      const browserSettingWindow = settingWins[0].win.browserWindow
      browserSettingWindow.webContents.send('settings::change-tab', category)
      if (isLinux) {
        browserSettingWindow.focus()
      } else {
        browserSettingWindow.moveTop()
      }
      return
    }
    this._createSettingWindow(category)
  }

  /**
   * Process file and related recent files to open
   * @param {string} filePath The main file path to open
   * @returns {{ rootDirectory: string, filesToOpen: string[] }} The directory and files to open
   */
  _processFileAndRecentFiles(filePath) {
    const rootDirectory = isDirectory2(filePath) ? filePath : path.dirname(filePath);
    const filesToOpen = [];

    // Add the main file first
    const mainFileInfo = normalizeMarkdownPath(filePath);
    if (mainFileInfo && !mainFileInfo.isDir) {
      filesToOpen.push(filePath);
    }

    // Get recently used documents
    const recentDocuments = this._accessor.menu.getRecentlyUsedDocuments();
    
    // Find up to 5 files from recent documents that belong to this directory
    let count = 1; // Start from 1 as we already have the main file
    for (let i = 0; i < recentDocuments.length && count < 5; i++) {
      const pathToCheck = recentDocuments[i];
      if (pathToCheck !== filePath && isChildOfDirectory(rootDirectory, pathToCheck)) {
        const info = normalizeMarkdownPath(pathToCheck);
        if (info && !info.isDir) {
          filesToOpen.push(pathToCheck);
          count++;
        }
      }
    }

    return { rootDirectory, filesToOpen };
  }

  _listenForIpcMain () {
    registerKeyboardListeners()
    registerSpellcheckerListeners()

    // add more language supports
    ipcMain.on('mt::language-changed', (e, lang) => {
      const win = BrowserWindow.fromWebContents(e.sender)
      console.log("------IPC 通信处理:系统层面收到渲染进程发出的语言切换请求--------");
      if (win) {
        try {
          this._accessor.language.setLanguage(lang)
          // 触发语言变更事件，让其他窗口知道语言已更改
          BrowserWindow.getAllWindows().forEach(window => {
            window.webContents.send('mt::language-update', lang)
          })
          win.reload()
        } catch (error) {
          log.error('Failed to change language:', error)
          win.webContents.send('mt::language-change-failed', error.message)
        }
      }
    })

    ipcMain.on('app-create-editor-window', () => {
      this._createEditorWindow()
    })

    ipcMain.on('screen-capture', async win => {
      if (isOsx) {
        // Use macOs `screencapture` command line when in macOs system.
        const screenshotFileName = await this.getScreenshotFileName()
        exec('screencapture -i -c', async (err) => {
          if (err) {
            log.error(err)
            return
          }
          try {
            // Write screenshot image into screenshot folder.
            const image = clipboard.readImage()
            const bufferImage = image.toPNG()
            await fsPromises.writeFile(screenshotFileName, bufferImage)
          } catch (err) {
            log.error(err)
          }
          win.webContents.send('mt::screenshot-captured')
        })
      } else {
        // TODO: Do nothing, maybe we'll add screenCapture later on Linux and Windows.
        // if (this.shortcutCapture) {
        //   this.launchScreenshotWin = win
        //   this.shortcutCapture.shortcutCapture()
        // }
      }
    })

    ipcMain.on('app-create-settings-window', category => {
      this._openSettingsWindow(category)
    })

    ipcMain.on('app-open-file-by-id', (windowId, filePath) => {
      console.log('ipcMain.on app-open-file-by-id')
      const openFilesInNewWindow = this._accessor.preferences.getItem('openFilesInNewWindow')
      if (openFilesInNewWindow) {
        const { rootDirectory, filesToOpen } = this._processFileAndRecentFiles(filePath);
        this._createEditorWindow(rootDirectory, filesToOpen)
      } else {
        const editor = this._windowManager.get(windowId)
        if (editor) {
          editor.openTab(filePath, {}, true)
        }
      }
    })
    
    ipcMain.on('app-open-files-by-id', (windowId, fileList) => {
      console.log('ipcMain.on app-open-files-by-id')
      const openFilesInNewWindow = this._accessor.preferences.getItem('openFilesInNewWindow')
      if (openFilesInNewWindow) {
        this._createEditorWindow(null, fileList)
      } else {
        const editor = this._windowManager.get(windowId)
        if (editor) {
          editor.openTabsFromPaths(
            fileList.map(p => normalizeMarkdownPath(p))
              .filter(i => i && !i.isDir)
              .map(i => i.path))
        }
      }
    })

    ipcMain.on('app-open-markdown-by-id', (windowId, data) => {
      console.log('ipcMain.on app-open-markdown-by-id')
      const openFilesInNewWindow = this._accessor.preferences.getItem('openFilesInNewWindow')
      if (openFilesInNewWindow) {
        this._createEditorWindow(null, [], [data])
      } else {
        const editor = this._windowManager.get(windowId)
        if (editor) {
          editor.openUntitledTab(true, data)
        }
      }
    })

    ipcMain.on('app-open-directory-by-id', (windowId, pathname, openInSameWindow) => {
      console.log('ipcMain.on app-open-directory-by-id')
      const { openFolderInNewWindow } = this._accessor.preferences.getAll()
      if (openInSameWindow || !openFolderInNewWindow) {
        const editor = this._windowManager.get(windowId)
        if (editor) {
          editor.openFolder(pathname)
          return
        }
      }
      
      const { rootDirectory, filesToOpen } = this._processFileAndRecentFiles(pathname);
      console.log('**Opening folder:', pathname,rootDirectory);
      this._createEditorWindow(rootDirectory, filesToOpen)
    })

    // --- renderer -------------------

    ipcMain.on('mt::app-try-quit', () => {
      app.quit()
    })

    ipcMain.on('mt::open-file-by-window-id', (e, windowId, filePath) => {
      const resolvedPath = normalizeAndResolvePath(filePath)
      const openFilesInNewWindow = this._accessor.preferences.getItem('openFilesInNewWindow')
      if (openFilesInNewWindow) {
        this._createEditorWindow(null, [resolvedPath])
      } else {
        const editor = this._windowManager.get(windowId)
        if (editor) {
          editor.openTab(resolvedPath, {}, true)
        }
      }
    })

    ipcMain.on('mt::select-default-directory-to-open', async e => {
      const { preferences } = this._accessor
      const { defaultDirectoryToOpen } = preferences.getAll()
      const win = BrowserWindow.fromWebContents(e.sender)

      const { filePaths } = await dialog.showOpenDialog(win, {
        defaultPath: defaultDirectoryToOpen,
        properties: ['openDirectory', 'createDirectory']
      })
      if (filePaths && filePaths[0]) {
        preferences.setItems({ defaultDirectoryToOpen: filePaths[0] })
      }
    })

    ipcMain.on('mt::open-setting-window', () => {
      this._openSettingsWindow()
    })

    ipcMain.on('mt::make-screenshot', e => {
      const win = BrowserWindow.fromWebContents(e.sender)
      ipcMain.emit('screen-capture', win)
    })

    ipcMain.on('mt::request-keybindings', e => {
      const win = BrowserWindow.fromWebContents(e.sender)
      const { keybindings } = this._accessor
      // Convert map to object
      win.webContents.send('mt::keybindings-response', Object.fromEntries(keybindings.keys))
    })

    ipcMain.on('mt::open-keybindings-config', () => {
      const { keybindings } = this._accessor
      keybindings.openConfigInFileManager()
    })

    ipcMain.handle('mt::keybinding-get-pref-keybindings', () => {
      const { keybindings } = this._accessor
      const defaultKeybindings = keybindings.getDefaultKeybindings()
      const userKeybindings = keybindings.getUserKeybindings()
      return { defaultKeybindings, userKeybindings }
    })

    ipcMain.handle('mt::keybinding-save-user-keybindings', async (event, userKeybindings) => {
      const { keybindings } = this._accessor
      return keybindings.setUserKeybindings(userKeybindings)
    })

    ipcMain.handle('mt::fs-trash-item', async (event, fullPath) => {
      return shell.trashItem(fullPath)
    })
  }
}

export default App
